package com.wondertek.onvif.retry;

import com.wondertek.onvif.config.DeviceConfigManager;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.net.ConnectException;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
import java.util.concurrent.Callable;
import java.util.function.Predicate;

/**
 * 重试机制管理器
 * 提供智能重试策略和异常处理
 */
@Slf4j
@Component
public class RetryManager {
    
    private static final Logger log = LoggerFactory.getLogger(RetryManager.class);
    
    @Autowired
    private DeviceConfigManager deviceConfigManager;
    
    /**
     * 可重试的异常类型
     */
    private static final Predicate<Throwable> RETRYABLE_EXCEPTIONS = throwable -> {
        return throwable instanceof SocketTimeoutException ||
               throwable instanceof ConnectException ||
               throwable instanceof UnknownHostException ||
               (throwable instanceof IOException && 
                (throwable.getMessage() != null && 
                 (throwable.getMessage().contains("Connection reset") ||
                  throwable.getMessage().contains("Connection refused") ||
                  throwable.getMessage().contains("Read timed out"))));
    };
    
    /**
     * 执行带重试的操作
     * 
     * @param operation 要执行的操作
     * @param config 设备配置
     * @param operationName 操作名称（用于日志）
     * @param <T> 返回类型
     * @return 操作结果
     * @throws Exception 最终失败时抛出异常
     */
    public <T> T executeWithRetry(Callable<T> operation, 
                                  DeviceConfigManager.DeviceConfig config, 
                                  String operationName) throws Exception {
        return executeWithRetry(operation, config.getRetryCount(), 
                              config.getRetryInterval(), operationName);
    }
    
    /**
     * 执行带重试的操作
     * 
     * @param operation 要执行的操作
     * @param maxRetries 最大重试次数
     * @param retryInterval 重试间隔（毫秒）
     * @param operationName 操作名称（用于日志）
     * @param <T> 返回类型
     * @return 操作结果
     * @throws Exception 最终失败时抛出异常
     */
    public <T> T executeWithRetry(Callable<T> operation, 
                                  int maxRetries, 
                                  int retryInterval, 
                                  String operationName) throws Exception {
        Exception lastException = null;
        
        for (int attempt = 0; attempt <= maxRetries; attempt++) {
            try {
                if (attempt > 0) {
                    log.debug("重试操作: {} (第 {}/{} 次)", operationName, attempt, maxRetries);
                }
                
                T result = operation.call();
                
                if (attempt > 0) {
                    log.info("操作重试成功: {} (第 {} 次尝试)", operationName, attempt + 1);
                }
                
                return result;
                
            } catch (Exception e) {
                lastException = e;
                
                // 检查是否为可重试的异常
                if (!RETRYABLE_EXCEPTIONS.test(e)) {
                    log.debug("遇到不可重试异常: {}, 操作: {}", e.getClass().getSimpleName(), operationName);
                    throw e;
                }
                
                // 如果已达到最大重试次数
                if (attempt >= maxRetries) {
                    log.warn("操作最终失败: {} (已重试 {} 次)", operationName, maxRetries);
                    break;
                }
                
                // 记录重试信息
                log.debug("操作失败，准备重试: {} (第 {}/{} 次), 异常: {}", 
                         operationName, attempt + 1, maxRetries, e.getMessage());
                
                // 等待重试间隔
                if (retryInterval > 0) {
                    try {
                        Thread.sleep(calculateRetryDelay(attempt, retryInterval));
                    } catch (InterruptedException ie) {
                        Thread.currentThread().interrupt();
                        throw new RuntimeException("重试被中断", ie);
                    }
                }
            }
        }
        
        // 抛出最后一次的异常
        throw new RetryExhaustedException(
            String.format("操作失败，已重试 %d 次: %s", maxRetries, operationName), 
            lastException);
    }
    
    /**
     * 执行带重试的操作（无返回值）
     * 
     * @param operation 要执行的操作
     * @param config 设备配置
     * @param operationName 操作名称（用于日志）
     * @throws Exception 最终失败时抛出异常
     */
    public void executeWithRetry(Runnable operation, 
                                DeviceConfigManager.DeviceConfig config, 
                                String operationName) throws Exception {
        executeWithRetry(() -> {
            operation.run();
            return null;
        }, config, operationName);
    }
    
    /**
     * 计算重试延迟时间（指数退避）
     * 
     * @param attempt 当前重试次数（从0开始）
     * @param baseInterval 基础间隔时间
     * @return 实际延迟时间
     */
    private long calculateRetryDelay(int attempt, int baseInterval) {
        // 指数退避：baseInterval * (2 ^ attempt)，最大不超过30秒
        long delay = (long) (baseInterval * Math.pow(2, attempt));
        return Math.min(delay, 30000);
    }
    
    /**
     * 检查异常是否可重试
     * 
     * @param throwable 异常
     * @return 是否可重试
     */
    public boolean isRetryable(Throwable throwable) {
        return RETRYABLE_EXCEPTIONS.test(throwable);
    }
    
    /**
     * 创建重试配置
     * 
     * @param maxRetries 最大重试次数
     * @param retryInterval 重试间隔
     * @return 重试配置
     */
    public RetryConfig createRetryConfig(int maxRetries, int retryInterval) {
        return new RetryConfig(maxRetries, retryInterval);
    }
    
    /**
     * 重试配置类
     */
    public static class RetryConfig {
        private final int maxRetries;
        private final int retryInterval;
        
        public RetryConfig(int maxRetries, int retryInterval) {
            this.maxRetries = maxRetries;
            this.retryInterval = retryInterval;
        }
        
        public int getMaxRetries() {
            return maxRetries;
        }
        
        public int getRetryInterval() {
            return retryInterval;
        }
    }
    
    /**
     * 重试耗尽异常
     */
    public static class RetryExhaustedException extends Exception {
        public RetryExhaustedException(String message, Throwable cause) {
            super(message, cause);
        }
    }
}